package org.jpropelleralt.jview.list.impl;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.Icon;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.box.Boxes;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.jview.JViews;
import org.jpropelleralt.jview.icon.IconFactory.IconSize;
import org.jpropelleralt.jview.messages.Messages;
import org.jpropelleralt.list.ListBox;
import org.jpropelleralt.ref.Ref;
import org.jpropelleralt.view.Views;
import org.jpropelleralt.view.update.UpdatableView;
import org.jpropelleralt.view.update.UpdateManager;

/**
 * An action allowing for an element of a {@link ListBox} to be moved by a set number of indices.
 * The {@link ListBox} is contained in a {@link Ref}, and the item moved
 * is selected by an integer index in another {@link Ref}.
 * @param <T>	The type of element in the {@link ListBox} 
 */
public class ListMoveAction<T> extends AbstractAction implements UpdatableView {

	private final static Icon iconUp = JViews.getIconFactory().getIcon(IconSize.SMALL, "actions", "go-up");
	private final static Icon iconDown = JViews.getIconFactory().getIcon(IconSize.SMALL, "actions", "go-down");

	private final int movement;
	private final Ref<ListBox<T>> list;
	private final Ref<Integer> selectionIndex;
	private final Ref<Boolean> locked;

	private final UpdateManager updateManager;

	private ListMoveAction(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex,
			Ref<Boolean> locked,
			int movement,
			String text, Icon icon,
            String desc, Integer mnemonic) {
		
		super(text, icon);
		
		if (movement == 0) throw new IllegalArgumentException("Cannot have movement of 0 - would do nothing");
		
		putValue(SHORT_DESCRIPTION, desc);
		
		if (mnemonic != null && mnemonic >= 0) {
			putValue(MNEMONIC_KEY, mnemonic);
		}
		
		this.list = list;
		this.selectionIndex = selectionIndex;
		this.movement = movement;
		this.locked = locked;

		updateManager = Views.updateManager();
		updateManager.registerUpdatable(this);
		
		//Listen to the list and selection, we can use this to
		//enable and disable the action
		list.features().addView(this);
		selectionIndex.features().addView(this);
		
		if (locked != null) {
			locked.features().addView(this);
		}
		
		//Start out updated
		updateManager.updateRequiredBy(this);

	}

	/**
	 * Create a {@link ListMoveAction}
	 * @param list				{@link Ref} to the {@link ListBox} to act on
	 * @param selectionIndex	{@link Ref} to the selection index of
	 * 							the item to MOVE.
	 * @param locked			{@link Ref} that controls editing - when true, 
	 * 							button is disabled, otherwise enabled. 
	 * 							If null, editing is always enabled.  
	 * @param movement			The number of indices the current selection will be 
	 * 							moved when the action is used
	 * @param text				Description text for action
	 * @param icon				Icon for action
	 * @param desc				Short description for action (e.g. for tooltip)
	 * @param mnemonic			Mnemonic for action, should be a {@link KeyEvent} 
	 * 							value, or negative to have no mnemonic
	 * 							e.g. {@link KeyEvent#VK_A}
	 * @return					A new {@link ListMoveAction}
	 * @param <T>				The type of element in the list 
	 */
	public static <T> ListMoveAction<T> create(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex,
			Ref<Boolean> locked,
			int movement,
			String text, Icon icon,
            String desc, Integer mnemonic) {
		return new ListMoveAction<T>(list, selectionIndex, locked, movement, text, icon, desc, mnemonic);
	}
	
	@Override
	public void changes(Map<Box, Change> changes) {
		updateManager.updateRequiredBy(this);
	}
	
	/**
	 * Create an action to move one index up (decrement index), with
	 * default icon. Text, mnemonic and tooltip set by resources.
	 * @param list				{@link Ref} to the {@link ListBox} to act on
	 * @param selectionIndex	{@link Ref} to the selection index of
	 * 							the item to move.
	 * @param locked			{@link Ref} that controls editing - when true, 
	 * 							button is disabled, otherwise enabled. 
	 * 							If null, editing is always enabled.  
	 * @return					A new {@link ListMoveAction} to move items up
	 * @param <T>				The type of element in the {@link ListBox} 
	 */
	public static <T> ListMoveAction<T> createUpAction(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex,
			Ref<Boolean> locked) {
		return new ListMoveAction<T>(
				list,
				selectionIndex,
				locked,
				-1, 
				Messages.getString("ListMoveAction.moveUpText"), 
				iconUp, 
				Messages.getString("ListMoveAction.moveUpDescription"), 
				Messages.getInt("ListMoveAction.moveUpMnemonic")); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Create an action to move one index down (increment index), with
	 * default icon. Text, mnemonic and tooltip set by resources.
	 * @param list				{@link Ref} to the {@link ListBox} to act on
	 * @param selectionIndex	{@link Ref} to the selection index of
	 * 							the item to move.
	 * @param locked			{@link Ref} that controls editing - when true, 
	 * 							button is disabled, otherwise enabled. 
	 * 							If null, editing is always enabled.  
	 * @return					A new {@link ListMoveAction} to move items down
	 * @param <T>				The type of element in the {@link ListBox} 
	 */
	public static <T> ListMoveAction<T> createDownAction(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex,
			Ref<Boolean> locked) {
		return new ListMoveAction<T>(
				list,
				selectionIndex,
				locked,
				1, 
				Messages.getString("ListMoveAction.moveDownText"), 
				iconDown, 
				Messages.getString("ListMoveAction.moveDownDescription"), 
				Messages.getInt("ListMoveAction.moveDownMnemonic")); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	/**
	 * Create an action to move one index up (decrement index), with
	 * default icon. Text, mnemonic and tooltip set by resources.
	 * @param list				{@link Ref} to the {@link ListBox} to act on
	 * @param selectionIndex	{@link Ref} to the selection index of
	 * 							the item to move.
	 * @return					A new {@link ListMoveAction} to move items up
	 * @param <T>				The type of element in the {@link ListBox} 
	 */
	public static <T> ListMoveAction<T> createUpAction(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex) {
		return createUpAction(list, selectionIndex, null);
	}

	/**
	 * Create an action to move one index down (increment index), with
	 * default icon. Text, mnemonic and tooltip set by resources.
	 * @param list				{@link Ref} to the {@link ListBox} to act on
	 * @param selectionIndex	{@link Ref} to the selection index of
	 * 							the item to move.
	 * @return					A new {@link ListMoveAction} to move items down
	 * @param <T>				The type of element in the {@link ListBox} 
	 */
	public static <T> ListMoveAction<T> createDownAction(
			Ref<ListBox<T>> list,
			Ref<Integer> selectionIndex) {
		return createDownAction(list, selectionIndex, null);
	}
	

	@Override
	public void actionPerformed(ActionEvent e) {
		
		//Perform actions atomically, in list universe
		list.features().universe().internal().acquire();
		try {
			
			if (Boxes.isTrue(locked)) {
				return;
			}
			
			int index = selectionIndex.get();
			ListBox<T> listBox = list.get();

			if (listBox == null) {
				return;
			}

			//Check current index is valid
			if (index < 0 || index >= listBox.size()) return;
			
			int target = index + movement;
			
			//Can't move outside list
			if (target < 0 || target >= listBox.size()) return;
			
			//Remove the element
			T element = listBox.remove(index);
			
			//Reinsert the element
			listBox.add(target, element);
			
			//TODO we can remove the need to manually adjust the selection
			//for increments and decrements (movement size 1). For example, for a movement
			//of 1, we can remove the element AFTER the selected element, then insert it
			//BEFORE the selected element, this will move the selected element without
			//deselecting it
			//Reselect the element (recalculate the target index since we may have adjusted it)
			selectionIndex.set(index + movement);
			
		} finally {
			list.features().universe().internal().release();
		}

	}

	@Override
	public void update() {
		int index = selectionIndex.get();
		ListBox<T> listBox = list.get();
		
		if (listBox == null) {
			setEnabled(false);
			return;
		}

		int target = index + movement;
		
		//Enabled when movement is possible, AND locked is false
		setEnabled((!Boxes.isTrue(locked)) && (index >= 0) && (index < listBox.size()) && (target >= 0) && (target < listBox.size()));
	}	
}
